﻿//Copyright © Steffen Zeidler 2010
using System.Collections.Generic;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;

namespace System.Threading
{
    /// <summary>Scheduler with virtualized time</summary>
    /// <example>
    /// IScheduler scheduler = TimeMachineScheduler.instance;
    /// scheduler.Interval(TimeSpan.FromMinutes(1)).Buffer(scheduler, TimeSpan.FromHours(1)).Take(3).Subscribe(x => Console.WriteLine(x.Average()));
    /// </example>
    public class TimeMachineScheduler : IScheduler
    {
        public IDisposable Schedule<TState>(TState state, DateTimeOffset dueTime, Func<IScheduler, TState, IDisposable> action)
        {
            Item<TState> entry;
            lock (sync)
            {
                //insert the action in the time sorted list
                int i = 0;
                for (; i < actions.Count && dueTime >= actions[i].time; i++) ;
                entry = new Item<TState>() { time = dueTime, scheduler = this, state = state, action = action };
                actions.Insert(i, entry);
                Monitor.PulseAll(sync);
            }
            return Disposable.Create(() =>
            {
                lock (sync) actions.Remove(entry);
            });
        }

        public IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
        {
            return Schedule(state, virtualTime + dueTime, action);
        }

        public IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
        {
            return Schedule(state, TimeSpan.Zero, action);
        }

        public DateTimeOffset Now { get { return virtualTime; } }

        public static readonly TimeMachineScheduler instance = new TimeMachineScheduler();

        private TimeMachineScheduler()
        {
            //start the scheduler
            Thread thread = new Thread(Run);
            thread.IsBackground = true;
            thread.Priority = ThreadPriority.BelowNormal;
            thread.Name = "Scheduler";
            thread.Start();
        }

        //scheduler
        private void Run()
        {
            while (true)
            {
                Action action;
                lock (sync)
                {
                    while (actions.Count == 0)
                    {
                        Monitor.Wait(sync);
                    }
                    if (virtualTime < actions[0].time)
                    {
                        //set the next virtual time
                        virtualTime = actions[0].time;
                    }
                    action = () => actions[0].Run();
                    actions.RemoveAt(0);
                }
                action();
                Thread.Sleep(1); //HACK
            }
        }

        private  abstract class Item
        {
            public DateTimeOffset time;
            public abstract IDisposable Run();
        }

        private class Item<TState> : Item
        {
            public TState state;
            public IScheduler scheduler;
            public Func<IScheduler, TState, IDisposable> action;
            public override IDisposable Run()
            {
                return action(scheduler, state);
            }
        }

        //time sorted action list
        private List<Item> actions = new List<Item>();
        //current virtual time
        private DateTimeOffset virtualTime = DateTimeOffset.MinValue;
        private readonly object sync = new object();
    }
}
